package com.chuangke.metadata.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.chuangke.admin.entity.SysDict;
import com.chuangke.admin.service.SysDictService;
import com.chuangke.common.constant.Masks;
import com.chuangke.common.exception.ChuangkeException;
import com.chuangke.common.express.CkEvaluatorExecutor;
import com.chuangke.common.express.CkExpression;
import com.chuangke.common.idgen.IdManager;
import com.chuangke.framework.util.TreeUtils;
import com.chuangke.metadata.dao.SysGridFormulaDao;
import com.chuangke.metadata.dao.SysGridModelDao;
import com.chuangke.metadata.dao.SysGridVarDao;
import com.chuangke.metadata.entity.SysGridColumn;
import com.chuangke.metadata.entity.SysGridFormula;
import com.chuangke.metadata.entity.SysGridModel;
import com.chuangke.metadata.entity.SysGridVar;
import com.chuangke.metadata.service.SysGridModelService;
import com.chuangke.metadata.service.SysGridlFormulaService;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Service
public class SysGridFormulaServiceImpl implements SysGridlFormulaService {

    private static final Logger logger = LoggerFactory.getLogger(SysGridFormulaServiceImpl.class);

    @Autowired
    private SysGridModelService sysGridModelService;

    @Autowired
    private SysGridModelDao sysGridModelDaoService;

    @Autowired
    private SysDictService sysDictService;

    @Autowired
    private SysGridFormulaDao sysGridFormulaDaoService;
    @Autowired
    private SysGridVarDao sysGridVarDaoService;

    @Override
    @Transactional(rollbackFor = Throwable.class)
    public void save(SysGridModel model) {
        sysGridFormulaDaoService
                .remove(Wrappers.<SysGridFormula>lambdaQuery().eq(SysGridFormula::getModelId, model.getId()));
        sysGridVarDaoService.remove(Wrappers.<SysGridVar>lambdaQuery().eq(SysGridVar::getModelId, model.getId()));

        if (!CollectionUtils.isEmpty(model.getVars())) {
            model.getVars().forEach(var -> {
                var.setId(IdManager.nextId());
                var.setModelId(model.getId());
            });
            sysGridVarDaoService.saveBatch(model.getVars());
        }

        if (!CollectionUtils.isEmpty(model.getFormulas())) {
            List<Integer> rowNumList = new ArrayList<>();
            model.getFormulas().forEach(formula -> {
                if (rowNumList.contains(formula.getRowNum())) {
                    throw new ChuangkeException(String.format("行号[%s]重复", formula.getRowNum()));
                } else {
                    rowNumList.add(formula.getRowNum());
                }
                formula.setId(IdManager.nextId());
                formula.setModelId(model.getId());
            });
            sysGridFormulaDaoService.saveBatch(model.getFormulas());
        }
    }

    @Override
    @Transactional(rollbackFor = Throwable.class)
    public void delete(List<SysGridFormula> list) {
        List<String> ids = list.stream().map(SysGridFormula::getId).collect(Collectors.toList());
        sysGridFormulaDaoService.removeBatchByIds(ids);
    }

    @Override
    public List<SysGridModel> loadFormulaByCategory(String category) {
        List<SysGridModel> list = sysGridModelDaoService
                .list(new QueryWrapper<SysGridModel>().lambda().eq(SysGridModel::getCategory, category));
        return list.stream().map(model -> loadFormulaByModelId(model.getId(), true)).collect(Collectors.toList());
    }

    @Override
    public List<SysGridModel> loadDataByCategory(String category, Map<String, Object> env) {
        List<SysGridModel> list = sysGridModelDaoService
                .list(new QueryWrapper<SysGridModel>().lambda().eq(SysGridModel::getCategory, category));

        if (CollectionUtils.isEmpty(list)) {
            throw new ChuangkeException("暂未配置相关业务预算信息");
        }

        return list.stream().map(model -> loadDataByModelId(model.getId(), env)).collect(Collectors.toList());
    }

    @Override
    public SysGridModel loadFormulaByModelId(String modelId, boolean isChangeLabel) {
        SysGridModel model = sysGridModelService.load(modelId);
        List<SysGridColumn> columns = model.getColumns();

        if (!CollectionUtils.isEmpty(columns)) {
            columns = TreeUtils.treeToList(columns);
            if (isChangeLabel) {
                columns.forEach(column -> {
                    if (!StrUtil.isBlank(column.getProp()) && column.getProp().length() > 3) {
                        column.setLabel(column.getLabel() + "(" + column.getProp().substring(3).toLowerCase() + ")");
                    }
                });
            }

            SysGridColumn rowNumColumn = new SysGridColumn();
            rowNumColumn.setAlign("center");
            rowNumColumn.setLabel("行号");
            rowNumColumn.setProp("rowNum");
            rowNumColumn.setWidth("60");
            rowNumColumn.setFixed("1");
            model.getColumns().add(0, rowNumColumn);
        }

        List<SysGridFormula> formulas = sysGridFormulaDaoService
                .list(new QueryWrapper<SysGridFormula>().lambda().eq(SysGridFormula::getModelId, modelId));
        formulas = formulas.stream().sorted(Comparator.comparing(SysGridFormula::getRowNum))
                .collect(Collectors.toList());
        model.setFormulas(formulas);

        List<SysGridVar> vars = sysGridVarDaoService
                .list(new QueryWrapper<SysGridVar>().lambda().eq(SysGridVar::getModelId, modelId));
        model.setVars(vars);

        return model;
    }

    @Override
    public SysGridModel loadDataByModelId(String modelId, Map<String, Object> env) {
        SysGridModel model = this.loadFormulaByModelId(modelId, false);

        resetFormulaByMasterData(model);

        calculate(model, env);

        model.setFormulas(model.getFormulas().stream().sorted(Comparator.comparing(SysGridFormula::getRowNum))
                .collect(Collectors.toList()));

        return model;
    }

    private void resetFormulaByMasterData(SysGridModel model) {
        if (!SysGridModel.TYPE_DYNAMIC.equals(model.getType())) {
            return;
        }

        if (StrUtil.isBlank(model.getDictName())) {
            throw new ChuangkeException("请在模型配置中维护好主数据名称");
        }

        if (CollectionUtils.isEmpty(model.getFormulas())) {
            throw new ChuangkeException("未定义模型公式");
        }

        if (model.getFormulas().size() > 1) {
            throw new ChuangkeException("模型公式只需要定义一行");
        }

        if (CollectionUtils.isEmpty(model.getColumns())) {
            throw new ChuangkeException("未定义视图模型列");
        }

        List<SysGridColumn> dictBindingColumns = model.getColumns().stream()
                .filter(column -> !StrUtil.isBlank(column.getDictProperty())).collect(Collectors.toList());
        if (CollectionUtils.isEmpty(dictBindingColumns)) {
            throw new ChuangkeException("视图模型列未绑定主数据属性");
        }

        logger.info("开始重新组织公式");

        Map<String, SysDict> dictMap = sysDictService.findMap(model.getDictName());
        List<SysDict> dictList;
        if (!StrUtil.isBlank(model.getDictFilter())) {
            dictList = dictMap.values().stream().filter(dict -> {
                Map<String, Object> env = com.chuangke.common.utils.BeanUtils.beanToMapSO(dict);

                Object result = CkEvaluatorExecutor.execute(model.getDictFilter(), env);
                if (!(result instanceof Boolean)) {
                    throw new ChuangkeException(String.format("数据筛选条件定义异常：%s", result));
                }

                return (Boolean) result;
            }).collect(Collectors.toList());
        } else {
            dictList = new ArrayList<>(dictMap.values());
        }

        List<SysGridFormula> formulas = new ArrayList<>();
        SysGridFormula oldFormula = model.getFormulas().get(0);
        for (int i = 0; i < dictList.size(); i++) {
            SysGridFormula newFormula = new SysGridFormula();
            BeanUtils.copyProperties(oldFormula, newFormula);

            for (SysGridColumn column : dictBindingColumns) {
                try {
                    String propValue = PropertyUtils.getSimpleProperty(dictList.get(i), column.getDictProperty())
                            .toString();
                    PropertyUtils.setProperty(newFormula, column.getProp(), propValue);
                } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
                    throw new ChuangkeException(e);
                }
            }
            newFormula.setRowNum(i + 1);
            formulas.add(newFormula);
        }

        model.setFormulas(formulas);

        logger.info("完成重新组织公式");
    }

    private void calculate(SysGridModel model, Map<String, Object> env) {
        handleRowLNFormula(model);

        List<String> varNameList = new Vector<>();
        List<CkExpression> expressionList = new Vector<>();

        initCalContext(model.getVars(), env);

        List<String> props = getAllProperty();

        logger.info("开始组织公式变量");

        Map<String, Object> formulaEnv = new ConcurrentHashMap<>();
        // 循环需要保存的数据集，分别将公式和数值保存到公式和变量中
        model.getFormulas().parallelStream().forEach(rowData -> {
            for (String prop : props) {
                // 目前只支持num字段定义公式
                if (!prop.startsWith("num")) {
                    continue;
                }

                String propValue;
                try {
                    propValue = (String) PropertyUtils.getSimpleProperty(rowData, prop);
                } catch (Exception e) {
                    throw new ChuangkeException(e);
                }

                if (StrUtil.isBlank(propValue)) {
                    continue;
                }

                String index = getIndex(rowData, prop);

                expressionList.add(new CkExpression(index, propValue));
                varNameList.addAll(CkEvaluatorExecutor.getVariableNames(propValue));

//				boolean isExpression = prop.startsWith("str") ? false : isExpression(propValue);
//				if (isExpression) {
//					expressionList.add(new CkExpression(index, propValue));
//					varNameList.addAll(CkEvaluatorExecutor.getVariableNames(propValue));
//				} else if (!StrUtil.isBlank(propValue)) {
//					formulaEnv.put(index, prop.startsWith("num") ? new BigDecimal(propValue) : propValue);
//				}
            }
        });
        env.putAll(formulaEnv);

        logger.info("完成组织公式变量");

        logger.info("开始初始不存在的变量");

        // 如果公式计算用到的变量没有值，默认赋值0,否则计算报错
        for (String varName : varNameList) {
            if (!env.containsKey(varName)) {
                env.put(varName, new BigDecimal("0"));
            }
        }

        logger.info("完成初始不存在的变量");

        logger.info("开始公式计算");

        // 公式计算
        Map<String, Object> obj = CkEvaluatorExecutor.execute(expressionList, env);

        logger.info("完成公式计算");

        logger.info("开始将结算结果回写");
        // 将计算出来的结果，返回去
        model.getFormulas().forEach(data -> {
            for (String prop : props) {
                try {
                    String index = getIndex(data, prop);
                    if (obj.containsKey(index)) {
                        PropertyUtils.setProperty(data, prop, obj.get(index) == null ? "" : obj.get(index).toString());
                    }
                } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
                    throw new ChuangkeException(e);
                }
            }
        });

        logger.info("是否显示空值" + model.getShowZero());
        if (!Masks.BOOL_YES.equals(model.getShowZero())) {
            List<SysGridFormula> dataList = model.getFormulas().stream().filter(data -> !isZero(data.getNumNa()) || !isZero(data.getNumNb()) || !isZero(data.getNumNc())
                    || !isZero(data.getNumNd()) || !isZero(data.getNumNe()) || !isZero(data.getNumNf())
                    || !isZero(data.getNumNg()) || !isZero(data.getNumNh()) || !isZero(data.getNumNi())
                    || !isZero(data.getNumNj()) || !isZero(data.getNumNk()) || !isZero(data.getNumNl())
                    || !isZero(data.getNumNm()) || !isZero(data.getNumNn()) || !isZero(data.getNumNo())
                    || !isZero(data.getNumNp()) || !isZero(data.getNumNq()) || !isZero(data.getNumNr())
                    || !isZero(data.getNumNs()) || !isZero(data.getNumNt())).collect(Collectors.toList());

            model.setFormulas(dataList);
        }

        logger.info("完成将结算结果回写");
    }

    private boolean isZero(String value) {
        if (StrUtil.isBlank(value)) {
            return true;
        }
        double dv = Double.parseDouble(value);
        return dv == 0;
    }

    /**
     * 处理行上获取单元格索引的公式
     *
     * @param model
     */
    private void handleRowLNFormula(SysGridModel model) {
        String rowIndexFunctionExpression = "(LN\\s*\\(\\s*\\w+\\s*\\))";
        Pattern p = Pattern.compile(rowIndexFunctionExpression);

        logger.info("开始处理行索引公式");
        model.getFormulas().forEach(rowData -> {
            Map<String, Object> dataMap = com.chuangke.common.utils.BeanUtils.beanToMapSO(rowData);
            for (Map.Entry<String, Object> entry : dataMap.entrySet()) {
                if (ObjectUtils.isEmpty(entry.getValue())) {
                    continue;
                }
                String oldExpression = entry.getValue().toString();
                String newExpression = entry.getValue().toString();
                Matcher m = p.matcher(oldExpression);
                boolean isMatched = false;
                while (m.find()) {
                    isMatched = true;
                    String lnGs = m.group();
                    String newValue = CkEvaluatorExecutor.execute(lnGs, dataMap).toString();
                    newExpression = newExpression.replace(lnGs, newValue);
                }
                if (isMatched) {
                    try {
                        PropertyUtils.setProperty(rowData, entry.getKey(), newExpression);
                    } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
                        throw new ChuangkeException(e);
                    }
                }
            }
        });

        logger.info("完成处理行索引公式");
    }

    /**
     * 获取所有动态字段熟悉
     *
     * @return
     */
    private List<String> getAllProperty() {
        PropertyDescriptor[] pds = BeanUtils.getPropertyDescriptors(SysGridFormula.class);
        return Arrays.stream(pds).map(PropertyDescriptor::getName).filter(p -> p.startsWith("str") || p.startsWith("num")).collect(Collectors.toList());
    }

    /**
     * 判断单元格数据是否为公式
     *
     * @param data
     * @return
     */
    @SuppressWarnings("unused")
    private boolean isExpression(String data) {
        if (StrUtil.isBlank(data)) {
            return false;
        }

        List<String> varList;
        try {
            varList = CkEvaluatorExecutor.getVariableNames(data);
        } catch (Exception e) {
            return false;
        }

        if (varList.isEmpty()) {
            return false;
        }

        if (varList.size() == 1) {
            return !data.equals(varList.get(0));
        } else {
            return true;
        }
    }

    /**
     * 初始化模型参数变量
     *
     * @param vars
     * @param env
     */
    private void initCalContext(List<SysGridVar> vars, Map<String, Object> env) {
        if (CollectionUtils.isEmpty(vars)) {
            return;
        }
        vars.forEach(svmv -> env.put(svmv.getVarKey(), svmv.getVarValue()));
    }

    /**
     * 获取单元格索引
     *
     * @param data
     * @param prop
     * @return
     */
    private String getIndex(SysGridFormula data, String prop) {
        return (prop.substring(3) + data.getRowNum()).toLowerCase();
    }

}
